package com.ddpt.permission.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lh.center.common.base.session.SessionUser;
import com.lh.center.common.enums.LogType;
import com.lh.center.common.enums.MarketingApp;
import com.lh.center.common.exception.CommonException;
import com.lh.center.common.util.AssertUtil;
import com.lh.center.common.util.IdUtil;
import com.lh.center.common.util.IpBelongWithinJuheUtil;
import com.lh.center.extensions.api.global.dubbo.DubboGlobalConfigService;
import com.lh.center.merchant.api.dubbo.DubboMerchantInsService;
import com.lh.center.merchant.api.dubbo.DubboMerchantService;
import com.lh.center.merchant.api.entity.Merchant;
import com.lh.center.merchant.api.entity.MerchantIns;
import com.lh.center.permission.QRCodeStatusEnum;
import com.lh.center.permission.api.entity.LoginLogEntity;
import com.lh.center.permission.api.entity.MerchantAdminUser;
import com.lh.center.permission.api.entity.StoreAdminUser;
import com.lh.center.permission.api.session.MerchantSessionUser;
import com.lh.center.permission.api.session.StoreSessionUser;
import com.lh.center.permission.dto.GetQRCodeDto;
import com.lh.center.permission.dto.GetWXBindStatusDto;
import com.lh.center.permission.dto.LoginByCodeDto;
import com.lh.center.permission.dto.LoginByQRCodeDto;
import com.lh.center.permission.service.LoginLogService;
import com.lh.center.permission.service.MerchantAdminUserService;
import com.lh.center.permission.service.StoreAdminUserService;
import com.lh.center.permission.service.login.MerchantLoginServiceImpl;
import com.lh.center.permission.service.login.StoreLoginServiceImpl;
import com.lh.center.permission.util.WXLoginUtil;
import com.lh.center.permission.vo.*;
import com.lh.center.store.api.dubbo.DubboStoreService;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 扫码登陆
 */
@RefreshScope
@RestController
@RequestMapping("/api/permission/login/qr/")
public class ScanCodeLoginController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private StoreAdminUserService storeAdminUserService;

    @Resource
    private MerchantAdminUserService merchantAdminUserService;

    @DubboReference
    private DubboMerchantService dubboMerchantService;

    @DubboReference
    private DubboMerchantInsService dubboMerchantInsService;

    @DubboReference
    private DubboStoreService dubboStoreService;

    @Resource
    private StoreLoginServiceImpl storeLoginService;

    @Resource
    private MerchantLoginServiceImpl merchantLoginService;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    private WXLoginUtil wxLoginUtil;

    @DubboReference
    private DubboGlobalConfigService dubboGlobalConfigService;

    @Value("${permission.login.enableScan}")
    private boolean enableScan;

    @Value("${permission.login.scanConfigId}")
    private Long scanConfigId;

    @Value("${spring.profiles.active}")
    private String activeEnv;

    //扫码登陆缓存前缀
    private static final String SCAN_CODE_LOGIN_ = "SCAN_CODE_LOGIN_";

    //扫码选择账号签发的临时凭证前缀
    private static final String SCAN_SELECT_ACCOUNT_ = "SCAN_SELECT_ACCOUNT_";

    //请求地址拼接的参数
    private static final String appendParams = "/merchant/#/qrcode_login_mobile";

    //商家地址拼接
    private static final String storeAppendParams = "/store/#/qrcode_login_mobile";


    /**
     * 生成二维码
     */
    @PostMapping("/getQRCode")
    public GetQRCodeVo getQRCode(@RequestBody GetQRCodeDto dto, HttpServletRequest request) {
        if (!enableScan) {
            throw new CommonException("当前系统未开启扫码登陆");
        }
        AssertUtil.isTrue(dto.getApp() != MarketingApp.merchant && dto.getApp() != MarketingApp.store, "暂不支持扫码登陆");
        String code = IdUtil.nextId().toString();
        stringRedisTemplate.opsForValue().set(SCAN_CODE_LOGIN_ + code, QRCodeStatusEnum.wait.name() + "$" + dto.getApp().name(), 3, TimeUnit.MINUTES);
        GetQRCodeVo vo = new GetQRCodeVo();
        String host = "";
        String origin = request.getHeader("Origin");
        String baseHost = "";

        baseHost = origin;


        if (dto.getApp() == MarketingApp.store) {
            host = baseHost + storeAppendParams + "?tag=" + code;
        } else if (dto.getApp() == MarketingApp.merchant) {
            host = baseHost + appendParams + "?tag=" + code;
        }

        //callBackUrl encode
        try {
            host = URLEncoder.encode(host, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        String url = wxLoginUtil.getScanCodePrefixUrl().replace("{appId}", wxLoginUtil.getAppId())
                .replace("{callbackUrl}", host);

        vo.setQrCode(url);
        vo.setTag(code);
        return vo;
    }

    /**
     * 获取二维码状态
     */
    @PostMapping("/getQRCodeStatus")
    public GetQRCodeStatusVo getQRCodeStatus(@RequestBody GetQRCodeVo dto) {
        String codeStatus = stringRedisTemplate.opsForValue().get(SCAN_CODE_LOGIN_ + dto.getTag());
        AssertUtil.isEmpty(codeStatus, "二维码已失效");
        GetQRCodeStatusVo vo = new GetQRCodeStatusVo();
        vo.setStatus(codeStatus.substring(0, codeStatus.indexOf("$")));
        if (QRCodeStatusEnum.valueOf(vo.getStatus()) == QRCodeStatusEnum.finished) {
            String userInfo = Base64.decodeStr(codeStatus.substring(codeStatus.lastIndexOf("$") + 1));
            String app = codeStatus.substring(codeStatus.indexOf("$") + 1, codeStatus.lastIndexOf("$"));

            if (app.equals(MarketingApp.store.name())) {
                vo.setSessionUser(JSONUtil.toBean(userInfo, StoreSessionUser.class));
            }
            if (app.equals(MarketingApp.merchant.name())) {

                MerchantSessionUser merchantSessionUser = JSONUtil.toBean(userInfo, MerchantSessionUser.class);

                Long merchantId = merchantSessionUser.getMerchantId();

                Merchant merchant = dubboMerchantService.getById(merchantId);
                if (merchant == null) {
                    throw new CommonException("无此商户");
                }
                merchantSessionUser.setMerchant(merchant);
                vo.setSessionUser(merchantSessionUser);
            }
        }

        return vo;
    }

    /**
     * 选择商家账号
     */
    @PostMapping("/selectStoreAccounts")
    public SelectStoreAccountsVo selectStoreAccounts(@RequestBody LoginByCodeDto dto) {
        AssertUtil.isEmpty(dto.getWxCode(), "流程错误");
        AssertUtil.isEmpty(dto.getTag(), "扫码标记缺失");
        String codeStatus = stringRedisTemplate.opsForValue().get(SCAN_CODE_LOGIN_ + dto.getTag());
        AssertUtil.isEmpty(codeStatus, "二维码已失效");
        //获取用户想要登陆的系统
        MarketingApp app = dto.getApp();
        //更新二维码状态
        stringRedisTemplate.opsForValue().set(SCAN_CODE_LOGIN_ + dto.getTag(), QRCodeStatusEnum.scanned.name() + "$" + app.name(), 5, TimeUnit.MINUTES);
        String openId = wxLoginUtil.getOpenId(dto.getWxCode());

        SelectStoreAccountsVo vo = new SelectStoreAccountsVo();

        ArrayList<StoreMerchantVo> accountList = new ArrayList<>();
        List<StoreAdminUser> stores = storeAdminUserService.list(Wrappers.lambdaQuery(StoreAdminUser.class)
                .eq(StoreAdminUser::getWxOpenId, openId)
        );
        AssertUtil.isTrue(CollectionUtils.isEmpty(stores), "暂无账号信息");
        for (StoreAdminUser store : stores) {
            //获取商户
            StoreMerchantVo merchantVo = new StoreMerchantVo();
            Merchant merchant = dubboMerchantService.getByMerchantId(store.getMerchantId());
            merchantVo.setName(merchant.getName());
            //获取账户
            StoreAccountVo accountVo = new StoreAccountVo();
            accountVo.setName(store.getStoreName());
            accountVo.setAccount(store.getUserName());
            //组装
            ArrayList<StoreAccountVo> accountVos = new ArrayList<>();
            accountVos.add(accountVo);
            merchantVo.setAccounts(accountVos);
            //和已有账户列表对比
            boolean flag = false;
            for (int i = 0; i < accountList.size(); i++) {
                //有相同商户名加账户
                if (accountList.get(i).getName().equals(merchantVo.getName())) {
                    accountList.get(i).getAccounts().add(accountVo);
                    flag = true;
                }
            }
            //没有则加商户
            if (!flag) {
                accountList.add(merchantVo);
            }
        }
        String certificate = UUID.randomUUID().toString().replace("-", "");
        //签发新的临时凭证，用于登陆
        stringRedisTemplate.opsForValue().set(SCAN_SELECT_ACCOUNT_ + certificate, dto.getTag(), 5, TimeUnit.MINUTES);
        vo.setCertificate(certificate);
        vo.setStoreAccounts(accountList);

        return vo;
    }

    /**
     * 选择商户账号
     */
    @PostMapping("/selectAccounts")
    public SelectAccountsVo selectAccounts(@RequestBody LoginByCodeDto dto) {
        AssertUtil.isEmpty(dto.getWxCode(), "流程错误");
        AssertUtil.isEmpty(dto.getTag(), "扫码标记缺失");
        String codeStatus = stringRedisTemplate.opsForValue().get(SCAN_CODE_LOGIN_ + dto.getTag());
        AssertUtil.isEmpty(codeStatus, "二维码已失效");
        //获取用户想要登陆的系统
        MarketingApp app = dto.getApp();
        //更新二维码状态
        stringRedisTemplate.opsForValue().set(SCAN_CODE_LOGIN_ + dto.getTag(), QRCodeStatusEnum.scanned.name() + "$" + app.name(), 5, TimeUnit.MINUTES);
        String openId = wxLoginUtil.getOpenId(dto.getWxCode());

        SelectAccountsVo vo = new SelectAccountsVo();

        List<AccountMerchantVo> accountList = new ArrayList();
        List<MerchantAdminUser> merchants = merchantAdminUserService.list(Wrappers.lambdaQuery(MerchantAdminUser.class)
                .eq(MerchantAdminUser::getWxOpenId, openId)
        );
        AssertUtil.isTrue(CollectionUtils.isEmpty(merchants), "暂无账号信息");

        //组装返回
        for (MerchantAdminUser item : merchants) {
            //获取商户
            AccountMerchantVo merchantVo = new AccountMerchantVo();
            Merchant merchant = dubboMerchantService.getByMerchantId(item.getMerchantId());
            merchantVo.setName(merchant.getName());
            //获取机构
            AccountInsVo insVo = new AccountInsVo();
            MerchantIns ins = dubboMerchantInsService.queryById(item.getInsId());
            insVo.setName(ins.getName());
            //获取账号
            AccountVo accountVo = new AccountVo();
            accountVo.setName(item.getRealName());
            accountVo.setAccount(item.getUsername());
            //组装account
            ArrayList<AccountVo> accountVos = new ArrayList<>();
            accountVos.add(accountVo);
            insVo.setAccounts(accountVos);

            //组装ins
            ArrayList<AccountInsVo> accountInsVos = new ArrayList<>();
            accountInsVos.add(insVo);
            merchantVo.setIns(accountInsVos);

            //和列表中账户比较并合并 flag代表是否已加入已有账号
            boolean flag = false;
            for (int i = 0; i < accountList.size(); i++) {
                if (accountList.get(i).getName().equals(merchantVo.getName())) {
                    List<AccountInsVo> insList = accountList.get(i).getIns();
                    //和商户机构列表对比 如果有相同机构则加账户
                    for (int j = 0; j < insList.size(); j++) {
                        if (insList.get(j).getName().equals(insVo.getName())) {
                            accountList.get(i).getIns().get(j).getAccounts().add(accountVo);
                            flag = true;
                        }
                    }
                    //没有相同机构则添加新机构
                    if (!flag) {
                        accountList.get(i).getIns().add(insVo);
                    }
                    flag = true;
                }
            }
            if (!flag) {
                accountList.add(merchantVo);
            }
        }

        String certificate = UUID.randomUUID().toString().replace("-", "");
        //签发新的临时凭证，用于登陆
        stringRedisTemplate.opsForValue().set(SCAN_SELECT_ACCOUNT_ + certificate, dto.getTag(), 5, TimeUnit.MINUTES);
        vo.setCertificate(certificate);
        vo.setMerchantAccounts(accountList);

        return vo;
    }

    /**
     * 正式登陆
     */
    @PostMapping("/loginByQRCode")
    public void loginByQRCode(HttpServletRequest request, @RequestBody LoginByQRCodeDto dto) {
        AssertUtil.isEmpty(dto.getCertificate(), "流程错误");
        AssertUtil.isEmpty(dto.getAccount(), "未选择账号");
        String certificate = stringRedisTemplate.opsForValue().get(SCAN_SELECT_ACCOUNT_ + dto.getCertificate());
        AssertUtil.isEmpty(certificate, "凭证错误");
        stringRedisTemplate.delete(SCAN_SELECT_ACCOUNT_ + dto.getCertificate());
        String tagValue = stringRedisTemplate.opsForValue().get(SCAN_CODE_LOGIN_ + certificate);
        AssertUtil.isEmpty(tagValue, "二维码已过期");
        MarketingApp app = MarketingApp.valueOf(tagValue.substring(tagValue.indexOf("$") + 1));

        SessionUser user = null;
        //处理登陆
        if (app == MarketingApp.store) {
            StoreAdminUser store = storeAdminUserService.get(Wrappers.lambdaQuery(StoreAdminUser.class).eq(StoreAdminUser::getUserName, dto.getAccount()));
            AssertUtil.isNull(store, "账号不存在");
            user = storeLoginService.loginByPasswordCallBack(store.getUserName(), store.getPassword());
            storeLoginLog(request, user, LogType.storeLogin);
        }

        if (app == MarketingApp.merchant) {
            MerchantAdminUser merchant = merchantAdminUserService.get(Wrappers.lambdaQuery(MerchantAdminUser.class).eq(MerchantAdminUser::getUsername, dto.getAccount()));
            AssertUtil.isNull(merchant, "账号不存在");
            user = merchantLoginService.loginByPasswordCallBack(merchant.getUsername(), merchant.getPassword());
            storeLoginLog(request, user, LogType.merchantLogin);
        }

        String encode = Base64.encode(JSONUtil.toJsonStr(user));
        stringRedisTemplate.opsForValue().set(SCAN_CODE_LOGIN_ + certificate, QRCodeStatusEnum.finished.name() + "$" + app.name() + "$" + encode, 1, TimeUnit.MINUTES);
    }

    /**
     * 获取当前用户微信绑定状态
     */
    @PostMapping("/getWXBindStatus")
    public GetWXBindStatusVo getWXBindStatus(@RequestBody GetWXBindStatusDto dto) {
        AssertUtil.isEmpty(dto.getApp(), "未携带app");
        AssertUtil.isEmpty(dto.getUserId(), "未携带用户id");
        GetWXBindStatusVo vo = new GetWXBindStatusVo();
        if (dto.getApp().equals("store")) {
            StoreAdminUser user = storeAdminUserService.getById(dto.getUserId());
            AssertUtil.isNull(user, "账户不存在");
            vo.setStatus(StringUtils.isEmpty(user.getWxOpenId()) ? 0 : 1);
        } else if (dto.getApp().equals("merchant")) {
            MerchantAdminUser user = merchantAdminUserService.getById(dto.getUserId());
            AssertUtil.isNull(user, "账户不存在");
            vo.setStatus(StringUtils.isEmpty(user.getWxOpenId()) ? 0 : 1);
        }
        return vo;
    }

    private <T> T storeLoginLog(HttpServletRequest request, SessionUser user, LogType type) {

        if (org.apache.commons.lang3.StringUtils.isNotBlank(activeEnv) && "poc".equals(activeEnv)) {
            // poc环境直接返回，不存日志。
            return (T) user;
        }

        Long userId = user.getUserId();
        String userName = "";
        String account = "";
        Long insId = null;
        if (LogType.merchantLogin.remark.equals(type.remark)) {
            MerchantSessionUser target = (MerchantSessionUser) user;
            userName = target.getUsername();
            account = target.getUsername();
            insId = target.getInsId();
        } else if (LogType.storeLogin.remark.equals(type.remark)) {
            StoreSessionUser target = (StoreSessionUser) user;
            userName = target.getUserName();
            account = target.getUserName();
        }

        if (StringUtils.isBlank(userName) && StringUtils.isBlank(account)) {
            throw new CommonException("非法登录");
        }

        // 获取merchant_id
        Long merchantId = user.getMerchantId();

        // 设置IP
        String clientIP = ServletUtil.getClientIP(request);

        // IP归属地
        String ipBelong = IpBelongWithinJuheUtil.getIpBelongWithinJuhe(clientIP);

        storeLog(userId, account, userName, "scanCode", String.valueOf(type), type.remark, clientIP, ipBelong, merchantId, insId);

        return (T) user;
    }

    private void storeLog(Long userId, String account, String userName, String loginType, String type, String typeRemark, String ip, String ipBelong, Long merchantId, Long insId) {
        LoginLogEntity log = new LoginLogEntity();
        log.setUserId(userId);
        log.setAccount(account);
        log.setUserName(userName);
        log.setLoginType(loginType);
        log.setType(type);
        log.setTypeRemark(typeRemark);
        log.setIp(ip);
        log.setIpBelong(ipBelong);
        log.setMerchantId(merchantId);
        log.setInsId(insId);
        loginLogService.saveOrUpdate(log);
    }

}
